package mongox

import (
	"context"
	"fmt"
	"gitee.com/zhongguo168a/go-nodex/dbx"
	"gitee.com/zhongguo168a/go-nodex/logx"
	"gitee.com/zhongguo168a/gocodes/datax/cachex"
	"gitee.com/zhongguo168a/gocodes/datax/coderx"
	"gitee.com/zhongguo168a/gocodes/datax/jsonmap"
	"gitee.com/zhongguo168a/gocodes/datax/reflectx"
	"gitee.com/zhongguo168a/gocodes/datax/schemax"
	"gitee.com/zhongguo168a/gocodes/myx/errorx"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
)

func JSONItemHandler() func(object interface{}, item map[string]interface{}) (interface{}, error) {
	return func(object interface{}, item map[string]interface{}) (interface{}, error) {
		err := jsonmap.MapToStruct(item, object)
		if err != nil {
			return nil, errorx.Wrap(err, "jsonmap.MapToStruct")
		}
		return object, err
	}
}
func CacheItemHandler() func(object interface{}, item map[string]interface{}) (interface{}, error) {
	return func(object interface{}, item map[string]interface{}) (interface{}, error) {
		target, ok := object.(cachex.IObject)
		if ok == false {
			return nil, errorx.New("object must cachex.IRoot")
		}
		target.RefUpdate(item)
		return target, nil
	}
}

func ReflectItemHandler() func(object interface{}, item map[string]interface{}) (interface{}, error) {
	return func(object interface{}, item map[string]interface{}) (interface{}, error) {
		target, ok := object.(reflectx.IRefObject)
		if ok == false {
			return nil, errorx.New("object must reflectx.IRefObject")
		}

		schema := schemax.GetDeclByKey(target.RefType())
		if schema == nil {
			err := jsonmap.MapToStruct(item, target)
			if err != nil {
				return nil, errorx.Wrap(err, "jsonmap.MapToStruct")
			}
		} else {
			err := coderx.NewMapToRef().SetSource(item).Write(target)
			if err != nil {
				return nil, errorx.Wrap(err, "MapToRef.Write")
			}
		}
		return target, nil
	}
}

func (req *Request) WithItemHandler(itemHandler func(object interface{}, item map[string]interface{}) (interface{}, error)) *Request {
	req.itemHandler = itemHandler
	return req
}
func (req *Request) WithCreator(itemCreator func() interface{}) *Request {
	req.itemCreator = itemCreator
	return req
}

func (req *Request) CountDocuments(query bson.M, opts ...*options.CountOptions) (count int, err error) {
	col, err := req.GetCollection()
	if err != nil {
		return
	}
	ctx := req.getContext()

	logx.Debug(fmt.Sprintf("[DB] CountDocuments FROM %v.%v QUERY=%v", req.table.Database, req.table.Name, query))
	icount, colerr := col.CountDocuments(ctx, query)
	if colerr != nil {
		if colerr.Error() == "mongo: no documents in result" {
			err = dbx.ErrNotFound
			return
		} else {
			err = colerr
			return
		}
	}

	count = int(icount)
	return
}

// *[]interface{}
// listx.IList
func (req *Request) Find(query bson.M, opts ...*options.FindOptions) (results []interface{}, err error) {
	col, err := req.GetCollection()
	if err != nil {
		return
	}
	ctx := req.getContext()
	logx.Debug(fmt.Sprintf("[DB] READLIST FROM %v.%v QUERY=%v", req.table.Database, req.table.Name, query))

	cur, err := col.Find(ctx, query, opts...)
	if err != nil {
		rerr := err
		if rerr.Error() == "mongo: no documents in result" {
			return nil, dbx.ErrNotFound
		} else {
			return nil, rerr
		}
	}
	defer cur.Close(context.Background())

	for cur.Next(ctx) {
		columns := map[string]interface{}{}
		err = cur.Decode(&columns)
		if err != nil {
			err = errorx.Wrap(err, "decode")
			return
		}
		pid, ok := columns["_id"].(primitive.ObjectID)
		if ok {
			columns["_id"] = pid.Hex()
		}

		jsonmap.MapToJsonMap(columns, columns)
		if req.itemHandler != nil && req.itemCreator != nil {
			var item = req.itemCreator()
			newitem, itemerr := req.itemHandler(item, columns)
			if itemerr != nil {
				return nil, itemerr
			}
			results = append(results, newitem)
		} else {
			results = append(results, columns)
		}
	}
	return
}

func (req *Request) FindOne(query bson.M, opts ...*options.FindOneOptions) (result interface{}, err error) {
	col, err := req.GetCollection()
	if err != nil {
		return nil, err
	}
	logx.Debug(fmt.Sprintf("[DB] READ FROM %v.%v QUERY=%v", req.table.Database, req.table.Name, query))
	r := col.FindOne(req.getContext(), query, opts...)
	if r.Err() != nil {
		rerr := r.Err()
		if rerr.Error() == "mongo: no documents in result" {
			return nil, dbx.ErrNotFound
		} else {
			return nil, rerr
		}
	}
	columns := map[string]interface{}{}
	err = r.Decode(&columns)
	if err != nil {
		return nil, errorx.Wrap(err, "decode")
	}
	pid, ok := columns["_id"].(primitive.ObjectID)
	if ok {
		columns["_id"] = pid.Hex()
	}
	jsonmap.MapToJsonMap(columns, columns)
	if req.itemHandler != nil && req.itemCreator != nil {
		var item = req.itemCreator()
		newitem, itemerr := req.itemHandler(item, columns)
		if itemerr != nil {
			return nil, itemerr
		}
		return newitem, nil
	}
	return columns, nil

}

func (req *Request) Aggregate(pipeline interface{}, opts ...*options.AggregateOptions) (results []interface{}, err error) {
	col, err := req.GetCollection()
	if err != nil {
		return nil, err
	}

	logx.Debug(fmt.Sprintf("[DB] Aggregate FROM %v.%v PIPELINE=%v", req.table.Database, req.table.Name, pipeline))
	cur, aggerr := col.Aggregate(context.Background(), pipeline, opts...)
	if aggerr != nil {
		return nil, errorx.Wrap(aggerr, "Aggregate")
	}
	defer cur.Close(context.Background())
	if cur.Err() != nil {
		rerr := cur.Err()
		if rerr.Error() == "mongo: no documents in result" {
			return nil, dbx.ErrNotFound
		} else {
			return nil, rerr
		}
	}

	for cur.Next(context.Background()) {
		columns := map[string]interface{}{}
		err = cur.Decode(&columns)
		if err != nil {
			return nil, errorx.Wrap(err, "decode")
		}
		pid, ok := columns["_id"].(primitive.ObjectID)
		if ok {
			columns["_id"] = pid.Hex()
		}
		jsonmap.MapToJsonMap(columns, columns)
		if req.itemHandler != nil && req.itemCreator != nil {
			var item = req.itemCreator()
			newitem, itemerr := req.itemHandler(item, columns)
			if itemerr != nil {
				return nil, itemerr
			}
			results = append(results, newitem)
		} else {
			results = append(results, columns)
		}

	}
	return
}
